BicyclesController   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Test Coverage

Coverage 96.88%

Importance

Changes 0
Metric Value
eloc 168
dl 0
loc 206
ccs 31
cts 32
cp 0.9688
rs 10
c 0
b 0
f 0
wmc 16

7 Functions

Rating   Name   Duplication   Size   Complexity  
B getAllBicycles 0 51 8
A getBikeById 0 25 1
A getBicyclesByCity 0 21 1
A updateBatchPositions 0 21 1
A updateBicycle 0 33 1
A createABike 0 16 1
A createManyBikes 0 27 3
1 8
import {
2
  Controller,
3
  Get,
4
  Post,
5
  Param,
6
  Patch,
7
  Body,
8
  Query,
9
  BadRequestException,
10
} from '@nestjs/common';
11 8
import {
12
  ApiBearerAuth,
13
  ApiOperation,
14
  ApiResponse,
15
  ApiParam,
16
  ApiBody,
17
  ApiTags,
18
  ApiQuery,
19
} from '@nestjs/swagger';
20
// we have removed all JwtAuthGuards from this route.
21
//  import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
22 8
import { BicyclesService } from './bicycles.service';
23 8
import { UpdateBicycleDto } from './dto/update-bicycle.dto';
24 8
import { Bicycle } from './entities/bicycle.entity';
25
import { BicycleResponse } from './types/bicycle-response.interface';
26 8
import { CreateBicycleDto } from './dto/create-bicycle.dto';
27 8
import { CityName } from 'src/cities/types/city.enum';
28 8
import { BatchUpdateBicyclePositionsDto, BicycleBatchResponseDto } from './dto/batch-update.dto';
29
30 8
const BIKE_ID = 'b1e77dd3-9fb9-4e6c-a5c6-b6fc58f59464';
31 8
const UNAUTHORIZED_ERROR_MESSAGE = 'Unauthorized. Authentication required';
32
33
@ApiTags('Bicycles')
34
@Controller({ path: 'bike', version: '1' })
35 8
export class BicyclesController {
36 14
  constructor(private readonly bicyclesService: BicyclesService) {}
37
38
  @Get()
39
  @ApiBearerAuth()
40
  @ApiOperation({ summary: 'Get all bicycles' })
41
  @ApiQuery({
42
    name: 'city',
43
    required: false,
44
    enum: CityName,
45
  })
46
  @ApiQuery({ name: 'lat', required: false, minimum: -90, maximum: 90 })
47
  @ApiQuery({ name: 'lon', required: false, minimum: -180, maximum: 180 })
48
  @ApiQuery({ name: 'radius', required: false, minimum: 0, maximum: 100000 })
49
  @ApiResponse({
50
    status: 200,
51
    description: 'List of bicycles',
52
    type: [Bicycle],
53
  })
54
  @ApiResponse({
55
    status: 401,
56
    description: 'Unauthorized. Authentication required',
57
  })
58 8
  async getAllBicycles(
59
    @Query('lat') lat?: string,
60
    @Query('lon') lon?: string,
61
    @Query('radius') radius?: string,
62
    @Query('city') city?: CityName,
63
  ): Promise<BicycleResponse[]> {
64 8
    const latitude = lat ? parseFloat(lat) : undefined;
65 8
    const longitude = lon ? parseFloat(lon) : undefined;
66 8
    const radi = radius ? parseFloat(radius) : 3000;
67
68 8
    if ((latitude && !longitude) || (longitude && !latitude)) {
69 2
      throw new BadRequestException('Both lat and lon must be provided for location search');
70
    }
71
72 6
    if (city) {
73 2
      if (latitude) {
74
        return this.bicyclesService.toBicycleResponses(
75
          await this.bicyclesService.findByCityAndLocation(city, latitude, longitude, radi),
76
        );
77
      }
78 2
      return this.bicyclesService.toBicycleResponses(await this.bicyclesService.findByCity(city));
79
    }
80
81 4
    if (latitude) {
82 2
      return this.bicyclesService.toBicycleResponses(
83
        await this.bicyclesService.findByLocation(latitude, longitude, radi),
84
      );
85
    }
86
87 2
    return this.bicyclesService.toBicycleResponses(await this.bicyclesService.findAll());
88
  }
89
90
  @Post('create')
91
  @ApiBearerAuth()
92
  @ApiOperation({ summary: 'Create a new bicycle' })
93
  @ApiBody({
94
    type: CreateBicycleDto,
95
    description: 'Bicycle creation data',
96
    required: false,
97
  })
98
  @ApiResponse({
99
    status: 201,
100
    description: 'Bicycle created successfully',
101
    type: Bicycle,
102
  })
103 8
  async createABike(@Body() createBicycleDto: CreateBicycleDto): Promise<Bicycle> {
104 7
    return await this.bicyclesService.createBike(createBicycleDto);
105
  }
106
107
  @Post('create-many')
108
  @ApiBearerAuth()
109
  @ApiOperation({
110
    summary: 'Create multiple bicycles',
111
    description:
112
      'Creates multiple bicycles in a single request. At least one bicycle must be provided.',
113
  })
114
  @ApiBody({
115
    type: [CreateBicycleDto],
116
    description: 'Array of bicycle creation data',
117
    required: true,
118
  })
119
  @ApiResponse({
120
    status: 201,
121
    description: 'Bicycles created successfully',
122
    type: [Bicycle],
123
  })
124
  @ApiResponse({
125
    status: 400,
126
    description: 'Bad Request - Empty array or invalid bicycle data provided',
127
  })
128 8
  async createManyBikes(@Body() createBicycleDto: CreateBicycleDto[]): Promise<Bicycle[]> {
129 2
    if (!createBicycleDto?.length) {
130 1
      throw new BadRequestException('At least one bike is required');
131
    }
132 1
    return await this.bicyclesService.createManyBikes(createBicycleDto);
133
  }
134
135
  @Get(':bikeId')
136
  @ApiBearerAuth()
137
  @ApiOperation({ summary: 'Get a bicycle by ID' })
138
  @ApiParam({
139
    name: 'bikeId',
140
    description: 'Unique identifier of the bicycle',
141
    type: 'string',
142
    example: BIKE_ID,
143
  })
144
  @ApiResponse({
145
    status: 200,
146
    description: 'Bicycle details retrieved successfully',
147
    type: Bicycle,
148
  })
149
  @ApiResponse({
150
    status: 401,
151
    description: UNAUTHORIZED_ERROR_MESSAGE,
152
  })
153
  @ApiResponse({
154
    status: 404,
155
    description: 'Bicycle not found',
156
  })
157 8
  async getBikeById(@Param('bikeId') id: string): Promise<Bicycle> {
158 4
    return await this.bicyclesService.findById(id);
159
  }
160
161
  @Patch(':bikeId')
162
  @ApiBearerAuth()
163
  @ApiOperation({ summary: 'Update bicycle by ID' })
164
  @ApiParam({
165
    name: 'bikeId',
166
    description: 'Unique identifier of the bicycle',
167
    type: 'string',
168
    example: BIKE_ID,
169
  })
170
  @ApiBody({
171
    description: 'Bicycle update details',
172
    type: UpdateBicycleDto,
173
  })
174
  @ApiResponse({
175
    status: 200,
176
    description: 'Bicycle updated successfully',
177
    type: Bicycle,
178
  })
179
  @ApiResponse({
180
    status: 400,
181
    description: 'Invalid input',
182
  })
183
  @ApiResponse({
184
    status: 404,
185
    description: 'Bicycle not found',
186
  })
187
  @ApiResponse({
188
    status: 401,
189
    description: UNAUTHORIZED_ERROR_MESSAGE,
190
  })
191 8
  async updateBicycle(@Param('bikeId') bikeId: string, @Body() updateBicycleDto: UpdateBicycleDto) {
192 4
    return this.bicyclesService.update(bikeId, updateBicycleDto);
193
  }
194
195
  @Patch('/batch/positions')
196
  @ApiOperation({ summary: 'Update multiple bicycle positions' })
197
  @ApiResponse({
198
    status: 200,
199
    description: 'Bicycle positions updated successfully',
200
    type: BicycleBatchResponseDto,
201
  })
202
  @ApiResponse({
203
    status: 400,
204
    description: 'Error: Bad Request (Invalid request payload)',
205
  })
206 8
  async updateBatchPositions(
207
    @Body() dto: BatchUpdateBicyclePositionsDto,
208
  ): Promise<BicycleBatchResponseDto> {
209 2
    const results = await this.bicyclesService.updatePositionsParallel(dto.updates);
210 2
    return {
211
      results,
212
      totalCount: results.length,
213 3
      successCount: results.filter((r) => r.success).length,
214 3
      failureCount: results.filter((r) => !r.success).length,
215
    };
216
  }
217
218
  @Get('city/:cityName')
219
  @ApiBearerAuth()
220
  @ApiOperation({ summary: 'Get all bicycles in a specific city' })
221
  @ApiResponse({
222
    status: 200,
223
    description: 'List of bicycles in the specified city',
224
    type: [Bicycle],
225
  })
226
  @ApiResponse({
227
    status: 401,
228
    description: UNAUTHORIZED_ERROR_MESSAGE,
229
  })
230
  @ApiParam({
231
    name: 'cityName',
232
    description: 'Name of the city',
233
    type: 'string',
234
    enum: CityName,
235
  })
236 8
  async getBicyclesByCity(@Param('cityName') cityName: CityName): Promise<Bicycle[]> {
237 1
    return await this.bicyclesService.findByCity(cityName);
238
  }
239
}
240